import java.io.*;
import java.net.*;
import java.util.BitSet;
import java.io.BufferedReader;

public class SerwerPoker {
	/* 
	 * -------------------------------------------------------------------------------------------------------
	 * 
	 *  Zmienne potrzebne do indentyfikacji serwera i polaczonego klienta
	 * 
	 * -------------------------------------------------------------------------------------------------------
	 */
	
	/*
	 * Do zrobienia
	 * 
	 * Wprowadzic ranking graczy
	 */
	ServerSocket socket_serwer = null;
	SvrClient klient[] = new SvrClient[5]; // moze byc maksymalnie 5ciu klientow
	
	Player gracz[] = new Player[5]; // human+bot
	BitSet moze_grac = new BitSet(5);
	Table stol;
	
	// parametry gry
	static short ileKlientow=0;
	//short ilePolaczonych = 0;
	static short ileBotow=0;
	static int wysokoscWpisowego=0;
	static int iloscZetonow=0;
	int zetonyKasyna=0;
	
	int etap=1;
	int start_game = 0;
	int gracz_perm = 0;
	BitSet winners = new BitSet(5);
	
	// kostruktor serwera
	SerwerPoker(int port){
		try{
			socket_serwer = new ServerSocket(port);
			System.out.println("Czekam na klientow...");
			listenSocket();
		}
		catch(IOException e)
		{
			System.out.println ("Nie mozna nasluchiwac portu "+port); 
			System.exit (-1);
		}
	}
	
	public boolean czySaKlienci()
	{
		for(int i=0; i < ileKlientow; i++)
			if(klient[i] != null)
				return true;
		return false;
	}
	
	// funkcja czekajaca na polaczenie wszystkich graczy i zaczynaj�ca gre
	public void listenSocket() {
		int clients = 0;
		moze_grac.clear();
		while (clients < ileKlientow) {
            try {
            	
                klient[clients] = new SvrClient (socket_serwer.accept(), this);
                klient[clients].Client_ID = (short)clients;
                
                gracz[clients] = new Human();
                gracz[clients].dajZetony(iloscZetonow);
                moze_grac.set(clients);
                
                sendMessageToAll("MSG Nowy gracz (ID: "+clients+") dolaczyl do gry");
				clients++;
            } 
            catch (IOException e) {
    			System.out.println ("Accept failed"); 
    		}
		}
		for(int i = 0; i < ileBotow; i++)
		{
			gracz[ileKlientow+i] = new Bot();
			gracz[ileKlientow+i].dajZetony(iloscZetonow);
			moze_grac.set(ileKlientow+i);
		}
		
		nextStepGame(false);
		
	}
	
	// zakonczenie dzialania serwera
	protected void finalize() {
		try {
			// rozlaczyc wszystkich klientow
			socket_serwer.close();
		} 
		catch (IOException e) {
			System.out.println ("Zamkniecie nie przebieglo pomyslnie."); 
			System.exit (-1);
		}
	}
	
	void akcjaBot()
	{
		int dec_bota = 0;
		if(etap == 2 || etap == 4)
		{
			dec_bota = gracz[gracz_perm].BotBetStrategy(stol);
			if(dec_bota == 0)
			{
				sendMessageToAll("MSG Gracz "+(gracz_perm)+" (Bot) wycofuje sie z gry.");
				moze_grac.clear(gracz_perm);
			}
			else if(dec_bota == 1)
				sendMessageToAll("MSG Gracz "+(gracz_perm)+" (Bot) wyrownuje.");
			else if(dec_bota == 2)
				sendMessageToAll("MSG Gracz "+(gracz_perm)+" (Bot) podbija do "+stol.stawka+".");
			else if(dec_bota == 3)
				sendMessageToAll("MSG Gracz "+(gracz_perm)+" (Bot) czeka.");
			else if(dec_bota == 4)
				sendMessageToAll("MSG Gracz "+(gracz_perm)+" (Bot) wchodzi ALL IN.");
			else if(dec_bota == 5)
				sendMessageToAll("MSG Gracz "+(gracz_perm)+" (Bot) stawia pierwsza stawke. Stawka "+stol.stawka+".");
		}
		else
		{
			if(gracz[gracz_perm].BotGameStrategy(stol))
				sendMessageToAll("MSG Gracz "+(gracz_perm)+" (Bot) zamienil karty.");
			else
				sendMessageToAll("MSG Gracz "+(gracz_perm)+" (Bot) nie zamienil kart.");
		}
	}
	
	/*
	 * Popracowac
	 */
	public int nextStepGame(boolean przel) // true - w przypadku, gdy opuszcze gre osoba, ktorej jest kolej i ktora zaczyna gre
	{
		if(etap == 1) //zaklady obowiazkowe
		{
			stol = new Table(gracz);
			stol.giveZetony(zetonyKasyna);
			for(int i = 0; i < ileKlientow+ileBotow; i++)
			{
				if(gracz[i] != null)
					moze_grac.set(i);
			}
			for(int i=0; i < gracz.length; i++)
			{
				if(gracz[i] != null)
					if(!stol.wezZetony(gracz[i], wysokoscWpisowego, 4))
					{
						moze_grac.clear(i);
					}
			}
			sendMessageToAll("MSG Wpisowe zostalo pobrane.");
			etap = 2;
			start_game = Math.abs((int)System.currentTimeMillis())%ileKlientow+ileBotow;
			/*while(moze_grac.get(start_game) == false)
			{
				start_game = (start_game+1)%ileKlientow;
			}*/
			if(moze_grac.get(start_game) == false) start_game = (moze_grac.nextSetBit(start_game) == -1)?moze_grac.nextSetBit(0):moze_grac.nextSetBit(start_game);
			gracz_perm = start_game;
			sendMessageToAll("CMD START");
			sendMessageToAll("MSG Karty zostaly rozdane.");
			sendMessageToAll("MSG Gre zaczyna Gracz "+gracz_perm+".");
			if(gracz_perm >= ileKlientow)
			{
				akcjaBot();
				return nextStepGame(false);
			}
			klient[gracz_perm].sendMessage("MSG Zaczynasz gre.");
			return 1;
		}
		else if(etap >= 2 && etap <= 4) // 2- licytacja I, 3 - wymiana kart, 4 - licytacja II
		{ 
			
			/*gracz_perm = (moze_grac.nextSetBit(gracz_perm+1) < ileKlientow && moze_grac.nextSetBit(gracz_perm+1) != -1)?moze_grac.nextSetBit(gracz_perm+1):moze_grac.nextSetBit(0);
			
			if(!moze_grac.get(start_game))
				start_game = (moze_grac.nextSetBit(start_game) >= ileKlientow)? moze_grac.nextSetBit(0) : moze_grac.nextSetBit(start_game) ;
			if(start_game == -1 || start_game >= ileKlientow) return 1;//System.exit(-1);//return 1;
			if(gracz_perm == start_game && !przel)
			{
				int dec_bota = 0;
				//--------------------------------------------------------------------------------------
				for(int i = 0; i < ileBotow; i++)
				{
					if(!moze_grac.get(ileKlientow+i)) continue;
					if(etap == 2 || etap == 4)
					{
						dec_bota = gracz[ileKlientow+i].BotBetStrategy(stol);
						if(dec_bota == 0)
						{
							sendMessageToAll("MSG Gracz "+(ileKlientow+i)+" (Bot) wycofuje sie z gry.");
							moze_grac.clear(ileKlientow+i);
						}
						else if(dec_bota == 1)
							sendMessageToAll("MSG Gracz "+(ileKlientow+i)+" (Bot) wyrownuje.");
						else if(dec_bota == 2)
							sendMessageToAll("MSG Gracz "+(ileKlientow+i)+" (Bot) podbija do "+stol.stawka+".");
						else if(dec_bota == 3)
							sendMessageToAll("MSG Gracz "+(ileKlientow+i)+" (Bot) czeka.");
						else if(dec_bota == 4)
							sendMessageToAll("MSG Gracz "+(ileKlientow+i)+" (Bot) wchodzi ALL IN.");
					}
					else
					{
						if(gracz[ileKlientow+i].BotGameStrategy(stol))
							sendMessageToAll("MSG Gracz "+(ileKlientow+i)+" (Bot) zamienil karty.");
						else
							sendMessageToAll("MSG Gracz "+(ileKlientow+i)+" (Bot) nie zamienil kart.");
					}
				}
				//--------------------------------------------------------------------------------------
				etap = (etap == 2)?3:(etap == 3 && !stol.ALLin)?4:5;
				if(etap == 5) return nextStepGame(false);
				if(!stol.ALLin) stol.check = true;
				//return 1;
			}
			sendMessageToAll("MSG Teraz Gracz "+gracz_perm+".");
			klient[gracz_perm].sendMessage("MSG Twoja kolej.");
			if(etap == 3) klient[gracz_perm].sendMessage("MSG Zaznacz wyzej jakie karty chcesz wymienic. By nie wymieniac zadnej, wcisnij przycisk 'wymien karty' bez zaznaczania.");
			return 1;*/
			//if(!(etap == 2 && gracz_perm == start_game && gracz_perm >= ileKlientow))
			gracz_perm = (moze_grac.nextSetBit((gracz_perm+1)%5) == -1)?moze_grac.nextSetBit(0):moze_grac.nextSetBit((gracz_perm+1)%5);
			if(gracz_perm == start_game && !przel)
			{
				etap = (etap == 2)?3:(etap == 3 && !stol.ALLin)?4:5;
				if(etap == 5) return nextStepGame(false);
				if(!stol.ALLin) stol.check = true;
			}
			if(gracz_perm >= ileKlientow)
			{
				akcjaBot();
				return nextStepGame(false);
			}
			sendMessageToAll("MSG Teraz Gracz "+gracz_perm+".");
			klient[gracz_perm].sendMessage("MSG Twoja kolej.");
			if(etap == 3) klient[gracz_perm].sendMessage("MSG Zaznacz wyzej jakie karty chcesz wymienic. By nie wymieniac zadnej, wcisnij przycisk 'wymien karty' bez zaznaczania.");
			return 1;
		}
		else if(etap == 5) // wylaniamy zwyciezce
		{
			winners = stol.compareAllPlayers(gracz, moze_grac);
			//winners.and(moze_grac);
			if(winners.cardinality() > 1)
			{
				sendMessageToAll("MSG ---------------//REMIS//---------------");
				sendMessageToAll("MSG Koniec gry");
				sendMessageToAll("MSG ");
				zetonyKasyna = stol.getPula();
				//return 1;
			}
			else
			{
				gracz_perm = winners.nextSetBit(0);
				sendMessageToAll("MSG ---------------//WYGRANA//---------------");
				sendMessageToAll("MSG Gracz "+gracz_perm+" wygral i zgarnia wygrana w wysokosci "+stol.getPula()+" !");
				stol.oddajPuleWygr(gracz[gracz_perm]);
				if(gracz_perm < ileKlientow)
					klient[gracz_perm].sendMessage("MSG Gratulacje ! Wygrales !");
			}
			gracz_perm = 0;
			etap = 1;
			
			for(int i = 0; i < ileKlientow+ileBotow; i++)
				if(gracz[i] != null)
					gracz[i].znieszczKarty();
			
			nextStepGame(false);
			return 1;
		}
		return 0;
	}
	
	public void sendMessageToAll(String mssg)
	{
		for(int i = 0; i < ileKlientow; i++)
		{
			if(klient[i]!=null) klient[i].sendMessage(mssg);
		}
	}
	
	
	public static void main(String[] args) {
		
		if(args.length < 4) return;
		try{
			ileKlientow = (short)Integer.parseInt(args[0]);
			ileBotow = (short)Integer.parseInt(args[1]);
			if(ileKlientow+ileBotow > 5) return;
			iloscZetonow= Integer.parseInt(args[2]);
			wysokoscWpisowego = Integer.parseInt(args[3]);
			if(wysokoscWpisowego >= iloscZetonow) return;
			new SerwerPoker(3535);
		}
		catch(NumberFormatException e)
		{
			System.out.println("Poprawne parametry [ilosc prawdziwych graczy] [ile botow] [ile zetonow kazdy dostaje na starcie] [wysokosc wpisowego]");
		}
	}

}

//Klasa odpowiadajaca za kazdego klienta, posiada tez komendy jakie moze uzyc klient
class SvrClient implements Runnable {
	// zmienne potrzebne do okreslenia klienta i wyadomosci in and out
	Socket socket_klient = null;
	BufferedReader dane_przychodzace = null;
	PrintWriter dane_wychodzace = null;
	SerwerPoker k_svr = null;
	Thread t;
	
	// parametry klienta
	short Client_ID;
	//int wygrane=0;
	String[] linia;
	String karty_send;
	boolean flaga=true;

    SvrClient (Socket socket, SerwerPoker server) {
        socket_klient = socket;
        k_svr = server;
        t = new Thread (this);
        t.start();
    }

	public void run() {
		try {
			dane_przychodzace = new BufferedReader (new InputStreamReader (socket_klient.getInputStream()));
			dane_wychodzace = new PrintWriter (socket_klient.getOutputStream(), true);
		} 
		catch (IOException e) {
			System.out.println ("Accept failed"); 
		}
		while (flaga) {
			try {
				linia = dane_przychodzace.readLine().split(" ");
				if(!k_svr.moze_grac.get(Client_ID) || k_svr.etap == 1) continue;
				// CHECK
				if(linia[0].equals("check"))
				{
					if(k_svr.gracz_perm == Client_ID)
					{
						if(k_svr.etap == 2 || k_svr.etap == 4)
						{
							if(k_svr.stol.check == true)
							{
								k_svr.sendMessageToAll("MSG Gracz "+Client_ID+" czeka.");
								k_svr.nextStepGame(false);
							}
							else
								sendMessage("MSG Stawka zostala juz wniesiona. Komenda niedostepna.");
						}
						else
							sendMessage("MSG Komende mozna uzyc tylko podczas licytacji.");
					}
					else
						sendMessage("MSG Komenda niedostepna. Poczekaj na swoja kolej.");
				}
				
				// BET - pierwsza stawka [przyjmuje tylko jeden parametr]
				else if(linia[0].equals("bet"))
				{
					if(k_svr.gracz_perm == Client_ID)
					{
						if(k_svr.stol.check && (k_svr.etap == 2 || k_svr.etap == 4) && !k_svr.stol.ALLin)
						{
							try{
								if(k_svr.stol.wezZetony(k_svr.gracz[Client_ID], Integer.parseInt(linia[1]), 0))
								{
									k_svr.sendMessageToAll("MSG Gracz "+Client_ID+" stawia pierwsze stawke. Stawka "+linia[1]+".");
									k_svr.nextStepGame(false);
									System.out.println("Stawka na stole "+k_svr.stol.stawka);
								}
								else
									sendMessage("MSG Podana liczba jest wieksza od ilosci zetonow na Twoim koncie lub ujemna");
							}
							catch(NumberFormatException e)
							{
								sendMessage("MSG Zostal wprowadzony zly format liczby calkowitej.");
							}
						}
						else
							sendMessage("MSG Komenda niedostepna w tym etapie gry.");
					}
					else
						sendMessage("MSG Komenda niedostepna. Poczekaj na swoja kolej.");
				}
				
				else if(linia[0].equals("raise"))
				{
					if(k_svr.gracz_perm == Client_ID)
					{
						if(!k_svr.stol.check && (k_svr.etap == 2 || k_svr.etap == 4))
						{
							try
							{
								if(k_svr.stol.wezZetony(k_svr.gracz[Client_ID], Integer.parseInt(linia[1]), 2))
								{
									k_svr.sendMessageToAll("MSG Gracz "+Client_ID+" podbija do "+linia[1]+".");
									k_svr.nextStepGame(false);
								}
								else
									sendMessage("MSG Komenda niedostepna w tym etapie gry.");
							}
							catch(NumberFormatException e)
							{
								sendMessage("MSG Zostal wprowadzony zly format liczby calkowitej.");
							}
						}
						else
							sendMessage("MSG Komenda niedostepna w tym etapie gry.");
					}
					else
						sendMessage("MSG Komenda niedostepna. Poczekaj na swoja kolej.");
				}
				
				else if(linia[0].equals("call"))
				{
					if(k_svr.gracz_perm == Client_ID)
					{
						if(!k_svr.stol.check && (k_svr.etap == 2 || k_svr.etap == 4))
						{
							if(k_svr.stol.wezZetony(k_svr.gracz[Client_ID], 0, 1))
							{
								k_svr.sendMessageToAll("MSG Gracz "+Client_ID+" wyrownuje.");
								k_svr.nextStepGame(false);
							}
							else
								sendMessage("MSG Komenda niedostepna w tym etapie gry.");
						}
						else
							sendMessage("MSG Komenda niedostepna w tym etapie gry.");
					}
					else
						sendMessage("MSG Komenda niedostepna. Poczekaj na swoja kolej.");
				}
				
				else if(linia[0].equals("fold"))
				{
					if(k_svr.gracz_perm == Client_ID)
					{
						if((k_svr.etap == 2 || k_svr.etap == 4))
						{
							k_svr.sendMessageToAll("MSG Gracz "+Client_ID+" wycofuje sie z gry.");
							k_svr.moze_grac.clear(Client_ID);
							k_svr.nextStepGame(false);
						}
						else
							sendMessage("MSG Komenda niedostepna w tym etapie gry.");
					}
					else
						sendMessage("MSG Komenda niedostepna. Poczekaj na swoja kolej.");
				}
				
				else if(linia[0].equals("allin"))
				{
					if(k_svr.gracz_perm == Client_ID)
					{
						if(!k_svr.stol.check && (k_svr.etap == 2 || k_svr.etap == 4))
						{
							if(k_svr.stol.wezZetony(k_svr.gracz[Client_ID], 0, 3))
							{
								k_svr.sendMessageToAll("MSG Gracz "+Client_ID+" wchodzi ALL IN.");
								k_svr.nextStepGame(false);
							}
							else
								sendMessage("MSG Komenda niedostepna w tym etapie gry.");
						}
						else
							sendMessage("MSG Komenda niedostepna w tym etapie gry.");
					}
					else
						sendMessage("MSG Komenda niedostepna. Poczekaj na swoja kolej.");
				}
				
				else if(linia[0].equals("zamienkarty"))
				{
					if(k_svr.gracz_perm == Client_ID)
					{
						if(k_svr.etap == 3)
						{
							try{
								if(Integer.parseInt(linia[1]) < 5 && Integer.parseInt(linia[1]) > 0)
								{
									for(int i = 0; i < Integer.parseInt(linia[1]); i++)
									{
										k_svr.stol.zamienKarte(k_svr.gracz[Client_ID], Integer.parseInt(linia[i+2]));
									}
									k_svr.sendMessageToAll("MSG Gracz "+Client_ID+" zamienil karty.");
									k_svr.nextStepGame(false);
								}
								else if(Integer.parseInt(linia[1]) == 0)
								{
									k_svr.sendMessageToAll("MSG Gracz "+Client_ID+" nie zamienil kart.");
									k_svr.nextStepGame(false);
								}
							}
							catch(NumberFormatException e)
							{
								sendMessage("MSG Niewlasciwy format liczby.");
							}
						}
						else
							sendMessage("MSG Komenda niedostepna w tym etapie gry.");
					}
					else
						sendMessage("MSG Komenda niedostepna. Poczekaj na swoja kolej.");
				}
				
				else if(linia[0].equals("pokazkarty"))
				{
					if(k_svr.etap >= 2 && k_svr.etap <= 4)
					{
						karty_send = "KRT";
						for(int i=0; i < 5; i++)
						{
							karty_send += " "+k_svr.gracz[Client_ID].pokazKarte(i);
						}
						sendMessage(karty_send);
					}
					else
						sendMessage("MSG Komenda niedostepna.");
				}
				else if(linia[0].equals("pokazzetony"))
				{
					if(k_svr.etap >= 2 && k_svr.etap <= 4)
					{
						karty_send = "ZET "+k_svr.gracz[Client_ID].Zetony();
						sendMessage(karty_send);
					}
					else
						sendMessage("MSG Komenda niedostepna.");
				}
				else{
					sendMessage("MSG Nie ma takiego polecenia.");
				}
								
			} 
			catch (IOException | NullPointerException e) {
				System.out.println ("Odczytanie klienta nie powiodlo sie");
				disconnectClient();
			} 
		}
	}
	
	public void sendMessage(String wiadomosc)
	{
		try{
			dane_wychodzace = new PrintWriter (socket_klient.getOutputStream(), true);
			dane_wychodzace.println(wiadomosc);
			System.out.println("INFO: Wiadomosc do klienta "+socket_klient.getRemoteSocketAddress()+" : "+wiadomosc);
		}
		catch(IOException e){
			System.out.println("ERROR: Wiadomosc do klienta "+socket_klient.getRemoteSocketAddress()+" nie zostala wyslana");
		}
	}
	
	@SuppressWarnings("deprecation")
	public void disconnectClient()
	{
		try {
			System.out.println("Rozlaczenie Gracza "+Client_ID);
			k_svr.sendMessageToAll("MSG Gracz "+Client_ID+" zostal rozlaczony.");
			k_svr.gracz[Client_ID] = null;
			k_svr.klient[Client_ID] = null;
			k_svr.moze_grac.clear(Client_ID);
			if(!k_svr.czySaKlienci())
			{
				k_svr.finalize();
				System.exit(-1);
			}
			if(Client_ID == k_svr.start_game)
			{
				k_svr.start_game = (k_svr.moze_grac.nextSetBit(Client_ID) == -1)?k_svr.moze_grac.nextSetBit(0):k_svr.moze_grac.nextSetBit(Client_ID);
				if(Client_ID == k_svr.gracz_perm)
					k_svr.nextStepGame(true);
			}
			else
				if(Client_ID == k_svr.gracz_perm)
					k_svr.nextStepGame(false);
			socket_klient.close();
			t.stop();
		}
		catch(IOException e){
			System.out.println ("Rozlaczenie klienta "+socket_klient.getRemoteSocketAddress()+" niepowiadlo sie");
		}
	}
}